/*
 * File Name: proxy.c
 *
 * Group Members: Cassie Chin (cassiechin9793@gmail.com)
 *                Stephen Smetana (stephensmetana11@gmail.com)
 *                Logan Widick (logan.widick@gmail.com)
 *
 * File Description: 
 *
 *
 * Sources: http://beej.us/guide/bgnet/examples/server.c
 *          This code is originally intended to send the string "Hello, World!\n"
 *          out over a stream connection.
 *
 *          We took the outline of this code and incorporated the ability to
 *          perform actions in a child process. This child process recieves
 *          HTTP requests from a client.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <stddef.h>
#include<sys/stat.h>
#include "httpstuff.h"

/* We can delete everything we don't need here before we turn in the project 
 Turn statement to ON to use any of these features */
#define DEBUG_OFF
#define SETSOCKOPT_OFF
#define REAP_DEAD_PROCESSES_OFF

#define PORT "60000"  // the port users will be connecting to
#define BACKLOG 10     // how many pending connections queue will hold
#define BUFFERSIZE 256
#define SVR_PORT "60001"
//#define SVR_IP "127.0.0.1"
#define SVR_IP "129.120.151.94" // this seems to consistantly be the ip for cse01
// note that "129.120.151.95" also seems to be the ip for cse02
// so we can have these addresses predifined here for testing. 

void main_child(int fd);

void sigchld_handler(int s) {
	while (waitpid(-1, NULL, WNOHANG) > 0)
		;
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa) {
	if (sa->sa_family == AF_INET) {
		return &(((struct sockaddr_in*) sa)->sin_addr);
	}

	return &(((struct sockaddr_in6*) sa)->sin6_addr);
}

int main(void) {
	int sockfd, new_fd; // listen on sock_fd, new connection on new_fd
	struct addrinfo hints, *servinfo, *p;
	struct sockaddr_storage their_addr; // connector's address information
	socklen_t sin_size;
	struct sigaction sa;
	int yes = 1;
	char s[INET6_ADDRSTRLEN];
	int rv;

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE; // use my IP

	if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
		return 1;
	}

	// loop through all the results and bind to the first we can
	for (p = servinfo; p != NULL; p = p->ai_next) {
		if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol))
				== -1) {
			perror("server: socket");
			continue;
		}

#ifdef SETSOCKOPT_ON
		if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
						sizeof(int)) == -1) {
			perror("setsockopt");
			exit(1);
		}
#endif

		if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
			close(sockfd);
			perror("server: bind");
			continue;
		}

		break;
	}

	if (p == NULL) {
		fprintf(stderr, "server: failed to bind\n");
		return 2;
	}

	freeaddrinfo(servinfo); // all done with this structure

	if (listen(sockfd, BACKLOG) == -1) {
		perror("listen");
		exit(1);
	}

#ifdef REAP_DEAD_PROCESSES_ON
	sa.sa_handler = sigchld_handler; // reap all dead processes
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART;
	if (sigaction(SIGCHLD, &sa, NULL) == -1) {
		perror("sigaction");
		exit(1);
	}
#endif

	printf("server: waiting for connections...\n");

	while (1) { // main accept() loop
		sin_size = sizeof their_addr;
		new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &sin_size);
		if (new_fd == -1) {
			perror("accept");
			continue;
		}

		inet_ntop(their_addr.ss_family,
				get_in_addr((struct sockaddr *) &their_addr), s, sizeof s);
		printf("server: got connection from %s\n", s);

		/* BEGIN CHILD PROCESS : HTTP request processing is done here*/
		if (!fork()) {
			close(sockfd); // child doesn't need the listener
			main_child(new_fd);
			close(new_fd);
			exit(0);
		}
		/* END CHILD PROCESS */
		close(new_fd); // parent doesn't need this
	}

	return 0;
}

void send501(FILE *sock_out_fp) {
	HTTPMsg *proxy_resp = new_HTTPMsg();
	proxy_resp->version = 1.0;
	proxy_resp->statusCode = 501;

	//make the body
	proxy_resp->fileptr = tmpfile();
	char tmp_body_buffer[BUFFER_SIZE+1];
	snprintf(tmp_body_buffer, BUFFER_SIZE, "<html><h1>501 Not Implemented</h1></html>\n");
	fwrite(tmp_body_buffer, sizeof(char), strlen(tmp_body_buffer), proxy_resp->fileptr);
	fflush(proxy_resp->fileptr);
	fseek(proxy_resp->fileptr, 0, SEEK_END);
	proxy_resp->contentLength = (int)ftell(proxy_resp->fileptr);

	// send over sock_out_fd
	HTTPMsg_sendMsg(proxy_resp, sock_out_fp);
	delete_HTTPMsg(proxy_resp);
}

void send503(FILE* sock_out_fp) {
	HTTPMsg *proxy_resp = new_HTTPMsg();
	proxy_resp->version = 1.0;
	proxy_resp->statusCode = 503;
	
//make the body
	proxy_resp->fileptr = tmpfile();
	char tmp_body_buffer[BUFFER_SIZE+1];
	snprintf(tmp_body_buffer, BUFFER_SIZE, "<html><h1>503 Error</h1></html>\n");
	fwrite(tmp_body_buffer, sizeof(char), strlen(tmp_body_buffer), proxy_resp->fileptr);
	fflush(proxy_resp->fileptr);
	fseek(proxy_resp->fileptr, 0, SEEK_END);
	proxy_resp->contentLength = (int)ftell(proxy_resp->fileptr);

	// send over sock_out_fd
	HTTPMsg_sendMsg(proxy_resp, sock_out_fp);
	delete_HTTPMsg(proxy_resp);
}

void main_child(int new_fd) {
	//    struct sockaddr_in client_address;
	HTTPMsg *recv_msg = NULL;
	HTTPMsg *proxy_resp = NULL;
	HTTPMsg *server_resp = NULL;

	FILE *proxy_to_server_fp = NULL;
	FILE *server_to_proxy_fp = NULL;
	char *filePath = NULL;

	//try to open file descriptors
	FILE* client_to_proxy_fp = fdopen(new_fd, "r");
	FILE* proxy_to_client_fp = fdopen(dup(new_fd), "w");
	if (!client_to_proxy_fp || !proxy_to_client_fp)
		exit(0);

	//The steps repeat for additional messages
	while (1) {
		//make new message
		recv_msg = new_HTTPMsg();
		recv_msg->isRequest = true;
		if (!recv_msg)
			break;

		// #2 -- complete
		HTTPMsg_doParsing(recv_msg, client_to_proxy_fp);

		//we should have reached either the BODY state or the END state.
		if (recv_msg->state != BODY && recv_msg->state != END) {
			delete_HTTPMsg(recv_msg);
			recv_msg = NULL;
			break;
		}

		// #3 -- complete
		// if not GET or HEAD, then both strcmps will be nonzero.
		if (strcmp(recv_msg->method, "GET") != 0
				&& strcmp(recv_msg->method, "HEAD") != 0) {
			send501(proxy_to_client_fp);
			delete_HTTPMsg(recv_msg);
			recv_msg = NULL;
		}

		else // if GET or HEAD
		{
			filePath = HTTPMsg_findFilePath(recv_msg);

			//no file path; send "Bad Request" message
			if (filePath == NULL) {
				send501(proxy_to_client_fp);
			}
			//file path exists. let's try to find the file.
			else {
				FILE *file_to_send = fopen(filePath, "r");
				//send from cache
				if (file_to_send) {
					//make message; set headers, version, status
					proxy_resp = new_HTTPMsg();
					proxy_resp->isRequest = false;
					proxy_resp->version = 1.0;
					proxy_resp->statusCode = 200;

					//index 1 skips over the . so we get /folder/file or /file.
					HeaderList_addHeader(proxy_resp->headerList, "Content-Location", &(filePath[1]));
					fseek(file_to_send, 0, SEEK_END);
					proxy_resp->contentLength = (int) ftell(file_to_send);
					
					//get: include file. head: do not include file; close file separately
					if (strcmp(recv_msg->method, "GET") == 0) {
						proxy_resp->fileptr = file_to_send;
						HTTPMsg_sendMsg(proxy_resp, proxy_to_client_fp);
					} else if (strcmp(recv_msg->method, "HEAD") == 0) {
						HTTPMsg_sendMsg(proxy_resp, proxy_to_client_fp);
						fclose(file_to_send);
					}
          delete_HTTPMsg(proxy_resp);
					proxy_resp = NULL;
				}

				//do not send from cache. request from server.
				else {
					//have we opened a connection?
					if (proxy_to_server_fp == NULL
							|| server_to_proxy_fp == NULL) {
						FilePointerSet mySet;
						if (openClientConnection(SVR_PORT, SVR_IP, &mySet) != 0) {
							send503(proxy_to_client_fp);
							break;
						} else {
							proxy_to_server_fp = mySet.outFile;
							server_to_proxy_fp = mySet.inFile;
						}
					}

					//we should have a connection. Send the request.
					if (HTTPMsg_sendMsg(recv_msg, proxy_to_server_fp) != 0) {
						//connection impossible; return 503
						send503(proxy_to_client_fp);
						break;
					}
		
					//now let's get the response
					server_resp = new_HTTPMsg();
					server_resp->isRequest = false;
					if(server_resp == NULL) break;
					HTTPMsg_doParsing(server_resp, server_to_proxy_fp);
					
					//check validity of response
					if (server_resp->state != BODY
							&& server_resp->state != END) {
						fflush(stdout);
						send503(proxy_to_client_fp);
						break;
					}

					//response ok, let's send it.
					HTTPMsg_sendMsg(server_resp, proxy_to_client_fp);
					
					//if we have a 2xx and a file pointer, save.
					if (server_resp->statusCode >= 200
							&& server_resp->statusCode < 300
							&& server_resp->fileptr) {
						char *newFilePath = HTTPMsg_findFilePath(server_resp);
						if(newFilePath)
						{
							free(filePath);
							filePath = NULL;	
						}			
						else{
							newFilePath = filePath;
							filePath = NULL;	//prevents double free.
						}			

						//find rightmost '/'
			
						int search_len = strlen(newFilePath);
						int i = 0;
						int slash_ind = -1;
						for (i = search_len - 1; i >= 0; i--) {
							if (newFilePath[i] == '\\' || newFilePath[i] == '/') {
								slash_ind = i;
								break;
							}
						}

						//Try to make the directory.
						if (slash_ind >= 0) {
							char *tmp_dirpath_str = calloc(sizeof(char),
									slash_ind + 1);
							if (tmp_dirpath_str) {
                strncpy(tmp_dirpath_str, &(newFilePath[0]), slash_ind);
								mkdir(tmp_dirpath_str, S_IRWXU | S_IRWXG);
                free(tmp_dirpath_str);
                tmp_dirpath_str = NULL;   
							}
							
						}

						//try to save the file
						FILE* local_save_file = fopen(newFilePath, "w");
						if (local_save_file) {
							rewind(server_resp->fileptr);
							char xfer_buffer[BUFFER_SIZE + 1];
							int num_xfer = 0;
							while (1) {
								num_xfer = fread(xfer_buffer, sizeof(char),
										BUFFER_SIZE, server_resp->fileptr);
								if (num_xfer <= 0)
									break;
								fwrite(xfer_buffer, sizeof(char), num_xfer,
										local_save_file);
								fflush(local_save_file);
							}
							fclose(local_save_file);
						}

						//now free new file path
						if (newFilePath) free(newFilePath);
            newFilePath = NULL;
					}
				}
			}
		}
		if (filePath)
			free(filePath);
		filePath = NULL;
		if (proxy_resp)
			delete_HTTPMsg(proxy_resp);
		proxy_resp = NULL;
		if (recv_msg)
			delete_HTTPMsg(recv_msg);
		recv_msg = NULL;
		if (server_resp)
			delete_HTTPMsg(server_resp);
		server_resp = NULL;
	}
	//cleanup
	if (filePath)
		free(filePath);
	if (proxy_to_server_fp)
		fclose(proxy_to_server_fp);
	if (server_to_proxy_fp)
		fclose(server_to_proxy_fp);
	if (proxy_to_client_fp)
		fclose(proxy_to_client_fp);
	if (client_to_proxy_fp)
		fclose(client_to_proxy_fp);
	if (proxy_resp)
		delete_HTTPMsg(proxy_resp);
	proxy_resp = NULL;
	if (recv_msg)
		delete_HTTPMsg(recv_msg);
	recv_msg = NULL;
	if (server_resp)
		delete_HTTPMsg(server_resp);
	server_resp = NULL;
    fflush(stdout);
}
